/**
 * @description: @vueuse/core 的 useFeatch 无法水合(2024.03.01没有兼容 nuxt)，所以暂时没有使用
 */
import { DANA_PACKAGE_ID } from "~~/config";
import type { FetchOptions, FetchContext } from "ofetch";
import { useGlobalStore } from "~~/store/global";
import { useEventBus, useCloned } from "@vueuse/core";
import * as MITO from "@zhj1214/qdjk-web";
import { usePageCacheStore } from "../store/modules/pageCache";
// import { showLoadingToast } from "vant"; 无法使用翻译
// 延迟执行
function delay(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

let globalStore: any = null;
let pageCache: any = null;

/**
 * 全局提示
 * 1. showLoadingToast 在请求结束以后自动化关闭,使用 loadingManual 字段可以进行手动控制
 * 2. 是否手动控制 showLoadingEmit,只需要在请求参数中加上 loadingManual 字段
 * 注意: 手动调用 showToast 也可以隐藏 loading
 * */
const showToastEmit = useEventBus("showToast-emit"); // 注册toast提醒（方便某些无法使用useI18n的场景）
const showLoadingEmit = useEventBus("showLoading-emit"); // 关闭全局的 loading
let isManualClose = false; // 是不是手动控制; 目的在于避免其他请求,错误提示顶掉当前的 loading
let isShowToastIng = false; // 是不是正在显示 toast;
// let showloadingId = ""; // 当前正在展示 showLoading 的唯一标识
//  提示信息逻辑
const toastFn = (message: any, id?: any) => {
  if (!message) return;
  // 1. 手动控制消息展示; 当然如果有错误消息还是要提示的,在请求回调中可以自行,顶掉这个提示
  if (isManualClose) {
    // // 1.1 消息拦截
    // if (showloadingId && !isShowToastIng && id.includes(showloadingId)) {
    //   // 1.2 2600毫秒内不能在隐藏信息,不然无法正常展示错误提示
    //   isShowToastIng = true;
    //   setTimeout(() => {
    //     isShowToastIng = false;
    //     showloadingId = "";
    //     isManualClose = false;
    //   }, 2200);
    //   // 1.3 自动展示错误信息
    //   showToastEmit.emit(message);
    // }
  }
  // 2. 程序自动控制逻辑
  else {
    // 2.1 自动-关闭全局 loading;  2024.08.06 当前版本 toast 还是可以顶掉 ShowLoading 的
    // if (!isShowToastIng) showLoadingEmit.emit();
    // 2.2 先执行一次隐藏,是为了隐藏 SHowLoading ,如果使用 2.1 的方式顶掉,那么其他接口 200 执行 showLoadingEmit.emit(); 就会立马隐藏 Toast
    showLoadingEmit.emit();
    // 2.2 拦截其他消息提示,只展示第一个
    if (!isShowToastIng) {
      // 这里的延迟 200 毫秒是由于showLoadingEmit.emit()执行需要时间,避免下面的 toast 也被隐藏
      setTimeout(() => {
        // 2.3 2600毫秒内不能在隐藏信息,不然无法正常展示错误提示
        isShowToastIng = true;
        setTimeout(() => {
          showLoadingEmit.emit(); // 手动场景不会有这行代码
          isShowToastIng = false;
        }, 2400);
        // 2.4 自动展示错误信息
        showToastEmit.emit(message);
      }, 100);
    }
  }
};

// 时区-偏移量
const offset = new Date().getTimezoneOffset();

/**
 * @description: 初始化定时器: 同步服务器时间
 */
let moneyIntervalTime: any = null;
let serverTime = 0;
const inItInterval = async () => {
  // 保存时间戳,让定时器增加
  if (!moneyIntervalTime) {
    moneyIntervalTime = setInterval(() => {
      serverTime += 1000;
      if (globalStore) {
        globalStore.$patch((state: any) => {
          state.serverTime = serverTime;
        });
      }
    }, 1000);
  }
};
process.client && inItInterval();

// 获取 body 对象
const getBody = (ctx: any) => {
  let param: any = null;
  if (ctx.options.body) {
    if (typeof ctx.options.body == "object") {
      const { cloned } = useCloned(ctx.options.body, { manual: true });
      // console.log("--克隆--", cloned);
      param = cloned.value;
      // param = ctx.options.body;
    } else {
      param = JSON.parse(ctx.options.body);
    }
  }
  return param;
};
// 请求前-拦截
const beforeFetch = async (ctx: {
  request: any;
  options: { headers: any };
}) => {
  // console.log("请求header：", ctx, typeof ctx);
  // 1. 增加请求 loading 的控制
  if (ctx.options.body) {
    try {
      let param: any = getBody(ctx);
      if (Object.hasOwnProperty.call(param, "loadingManual")) {
        isManualClose = param.loadingManual;
        // 当前正在展示 showLoading 的唯一标识
        // if (isManualClose) {
        //   showloadingId = ctx.request;
        // }
        delete param.loadingManual;
        // console.log(param, "请求header：", ctx);
        if (typeof ctx.options.body == "object") {
          ctx.options.body = param;
        } else {
          ctx.options.body = JSON.stringify(param);
        }
      }
    } catch (error) {
      isManualClose = false;
    }
  }
  // 2. 修改请求头
  ctx.options.headers = {
    ...ctx.options.headers,
    Accept: "application/json",
    token: globalStore?.$state.token || "",
    Language: globalStore?.$state.language || "en",
    packageId: globalStore?.$state.packageId || DANA_PACKAGE_ID.value,
    "Package-Id": globalStore?.$state.packageId || DANA_PACKAGE_ID.value,
  };
};

/**
 * @description: 请求结束-拦截
 * @param {FetchContext} ctx 请求信息
 * 场景: 1. 由于官方 keepalive 有bug,暂时利用这个来保证,某些不变数据的加载速度
 * 1. 给接口的信息增加缓存功能: cache: true 保存到 store 中,获取也是 store 中的数据; false 这重新请求数据,并删除之前的缓存
 */
const afterFetch = async (ctx: FetchContext) => {
  // console.log("ctx.response:", ctx);
  // 2. 业务逻辑处理
  if (ctx.response!.ok || ctx.response!.status === 200) {
    // console.log("接口返回信息:", ctx);
    // 2.1 更新服务器时间
    if (ctx.response.headers) {
      const d = ctx.response.headers.get("date");
      if (d) {
        const timestamp = new Date(d).getTime();
        if (timestamp > serverTime) serverTime = timestamp;
      }
    }

    // 2.2 缓存数据 默认 5 分钟过期
    const { code, data, message } = ctx.response?._data || {};
    let param: any = getBody(ctx);
    if (param && param.cache === true && code == 200) {
      pageCache.addPageData(
        `${ctx.request}`.replace(`${ctx.options.baseURL}`, ""),
        ctx.response?._data.data ? ctx.response?._data.data : true,
        1000 * 60 * 5
      );
    }

    // 2.3 业务 code 处理
    let msg = "";
    // 获取 data 中的错误消息
    if (message && message != "Tips" && message != "tips") {
      msg = message;
    } else if (data) {
      if (data.errors) {
        msg = data.errors;
      }
    }

    if (!msg && ctx.response?._data.data && ctx.response?._data.data.error) {
      msg = ctx.response?._data.data.error;
    }
    if (code) {
      switch (code) {
        case 500: {
          toastFn(msg, ctx.request);
          ctx.response!._data = null;
          break;
        }
        case 1001: {
          toastFn(msg, ctx.request);
          ctx.response!._data = null;
          break;
        }
        // 场景: 邮箱注册,当前使用的邮箱,已经注册了
        case 1003: {
          toastFn(msg, ctx.request);
          ctx.response!._data = 1003;
          break;
        }

        // 请求 reponse 超时,等网络异常,或者数据不同步现象,刷新页面请求
        case 1203: {
          toastFn(msg, ctx.request);
          ctx.response!._data = 1203;
          break;
        }
        // 抽奖下注,无法提示后端的错误信息,暂时这么处理
        case 1204: {
          if (msg) {
            isShowToastIng = true;
            showToastEmit.emit(msg);
            setTimeout(() => {
              isShowToastIng = false;
            }, 2200);
          }
          ctx.response!._data = 1204;
          break;
        }
        // 服务端 api错误，不做处理直接返回
        case 400: {
          toastFn(msg, ctx.request);
          if (ctx.response?._data && ctx.response?._data.data) {
            ctx.response!._data = ctx.response?._data.data;
          } else {
            ctx.response!._data = null;
          }
          break;
        }
        // 未登录
        case 403: {
          console.error("http 用户 token 失效!!");
          jumpLogin();
          ctx.response!._data = null;
          break;
        }
        // 服务错误，暂不提示，影响用户体验（后续再看）
        case 404: {
          toastFn(msg, ctx.request);
          ctx.response!._data.data = null;
          break;
        }

        // 请求成功, 将 data 直接返回
        case 200: {
          // 1. 不是手动控制的话,隐藏 loading
          if (!isManualClose) showLoadingEmit.emit();

          // 2. 赋值
          if (ctx.response?._data && ctx.response?._data.data) {
            ctx.response!._data = ctx.response?._data.data;
          } else {
            ctx.response!._data = true;
          }

          break;
        }
        // 其他失败情况
        default:
          break;
      }
    } else {
      console.log("类型：", typeof ctx.response?._data);
    }
  }
};

// 请求：失败
const onRequestError = (ctx: any) => {
  console.log("❌请求失败：", ctx);
  process.client &&
    MITO.log({
      message:
        (ctx.request || "") +
        " ~RequestError~ " +
        (ctx.error ? ctx.error.message : ""),
      tag: "请求发送Request失败",
      level: "low",
      ex: ctx,
    });
  // 2. 提示
  toastFn("common.networkAnomaly", ctx.request);
};

// 响应：失败
const onResponseError = (ctx: any) => {
  // console.log("❌响应失败：", ctx);
  process.client &&
    MITO.log({
      message:
        (ctx.response ? ctx.response.url : "") +
        " ~ResponseError~ " +
        (ctx.response ? ctx.response._data : ""),
      tag: "返回失败Response",
      level: "low",
      ex: ctx,
    });
  // 2. 提示
  toastFn("common.networkAnomaly", ctx.request);
};

// 取消请求
const newAbort = (call?: any) => {
  const control = new AbortController();
  call(control);
  return control.signal;
};

class HttpRequest {
  static baseURL: string = "";
  static timeout: number = process.env.NODE_ENV == "development" ? 3000 : 15000;
  // 默认 header
  static headers?: Record<string, string> = {
    Language: "en",
    "Content-Type": "application/json;charset=UTF-8",
    TimeZone: `GMT${offset > 0 ? "-" : "+"}${Math.abs(offset / 60)}`, // morgan 要求传 GMT+x   // timezone 插件可以使用这个获取。 dayjs?.tz?.guess() => Timezone: Asia/Shanghai
  };
  // 配置项
  options: FetchOptions = {};
  constructor(doMain?: string, timeout?: number) {
    let baseURL = undefined;
    // 1. 获取配置参数
    if (doMain === "thirdParty") {
      baseURL = "https://baidu.com";
    }
    // 日志
    else if (doMain === "log") {
      baseURL = "";
    }
    // elk 平台
    else if (doMain === "elk") {
      // 本地测试外网:104.199.239.207:5044
      // 测试/生产内网10.140.0.67:5044
      baseURL = "https://104.199.239.207:5044/api";
    }
    // bdc 模块
    else if (doMain === "bdc") {
      baseURL = "/api";
    }
    if (timeout) HttpRequest.timeout = timeout;

    // 2. 创建请求实例
    this.options = {
      baseURL,
      timeout: HttpRequest.timeout,
      headers: {
        ...RequestFetch.headers,
      },
      retry: 0, // 请求出错，自动重试
      onRequestError,
      onResponseError,
      onRequest: beforeFetch,
      onResponse: afterFetch,
    };
  }

  async fetch<T = any>(params?: any, call?: any) {
    if (!globalStore) globalStore = useGlobalStore();
    const signal = (call && newAbort(call)) || undefined;
    if (!params.method) params.method = "get";

    console.log("this", this);
    return $fetch<T>(params.url, {
      ...this.options,
      signal,
      params: params.method == "get" ? params.data : undefined,
      body: params.method == "post" ? params.data : undefined,
      method: params.method,
    });
  }

  /**
   * @description: 请求之前-公共逻辑处理
   * 1. 初始化 globalStore
   * 2. 接口缓存 cache
   */
  beforeRequest(url: string, body: any) {
    // console.log(body, "--进来了--", url);
    if (!globalStore) globalStore = useGlobalStore();
    // 缓存
    if (body && body.cache) {
      if (!pageCache) pageCache = usePageCacheStore();
      const cacheRes = pageCache.getPageData(url);
      // console.log("--取到数据--", cacheRes);
      showLoadingEmit.emit(); // 如果存在 loading 直接隐藏
      if (cacheRes) return cacheRes;
    }
    return false;
  }

  // 用来兼容之前的写法
  Call<T = any>(params?: any, call?: any) {
    return () => {
      if (!globalStore) globalStore = useGlobalStore();
      const signal = (call && newAbort(call)) || undefined;
      try {
        return $fetch<T>(params.url, {
          ...this.options,
          signal,
          params: params.method == "get" ? params.data : undefined,
          body: params.method == "post" ? params.data : undefined,
          method: params.method,
        });
      } catch (error) {
        return false;
      }
    };
  }

  Get<T = any>(url: string) {
    return async (params?: any, call?: any) => {
      const cacheRes = await this.beforeRequest(url, params);
      if (cacheRes) return cacheRes;
      const signal = (call && newAbort(call)) || undefined;
      try {
        return $fetch<T>(url, {
          ...this.options,
          signal,
          params,
          method: "GET",
        });
      } catch (error) {
        return false;
      }
    };
  }

  Post<T = any>(url: string, headers?: any) {
    return async (body?: any, call?: any) => {
      if (!globalStore) globalStore = useGlobalStore();
      const cacheRes = this.beforeRequest(url, body);
      if (cacheRes) return cacheRes;
      const signal = (call && newAbort(call)) || undefined;
      try {
        // 请求头合并
        if (headers) {
          this.options.headers = { ...this.options.headers, ...headers };
        }

        return await $fetch<T>(url, {
          ...this.options,
          signal,
          body,
          method: "POST",
        });
      } catch (error) {
        return false;
      }
    };
  }

  Put<T = any>(url: string) {
    return async (body?: any, call?: any) => {
      const cacheRes = await this.beforeRequest(url, body);
      if (cacheRes) return cacheRes;
      const signal = (call && newAbort(call)) || undefined;
      try {
        return $fetch<T>(url, { ...this.options, signal, body, method: "PUT" });
      } catch (error) {
        return false;
      }
    };
  }

  Delete<T = any>(url: string) {
    return async (body?: any, call?: any) => {
      const cacheRes = await this.beforeRequest(url, body);
      if (cacheRes) return cacheRes;
      const signal = (call && newAbort(call)) || undefined;
      try {
        return $fetch<T>(url, {
          ...this.options,
          signal,
          body,
          method: "DELETE",
        });
      } catch (error) {
        return false;
      }
    };
  }
  Patch<T = any>(url: string) {
    return async (body?: any, call?: any) => {
      const cacheRes = await this.beforeRequest(url, body);
      if (cacheRes) return cacheRes;
      const signal = (call && newAbort(call)) || undefined;

      try {
        return $fetch<T>(url, {
          ...this.options,
          signal,
          body,
          method: "PATCH",
        });
      } catch (error) {
        return false;
      }
    };
  }
}

const http = new HttpRequest("bdc");

const { fetch } = http;

export { fetch, HttpRequest, http };
